const path = require('path');
const userHome = require('user-home');
const pathExists = require('path-exists');
const fse = require('fs-extra');
const fs = require('fs');
const glob = require('glob');
const ejs = require('ejs');
const pkgUp = require('pkg-up');
const semver = require('semver');
const Package = require('@yuyd-cli-dev/package');

const Command = require('@yuyd-cli-dev/command');
const inquirer = require('inquirer');
const log = require('@yuyd-cli-dev/log');
const request = require('@yuyd-cli-dev/request');
const { sleep, spinnerStart, execAsync } = require('@yuyd-cli-dev/utils');

// 测试数据，现在已从api接口获取
// const PAGE_TEMPLATE = [
//   {
//     name: 'Vue2首页模板',
//     npmName: 'yuyd-cli-dev-template-page-vue2',
//     version: '1.0.0',
//     targetPath: 'src/views/Home',
//     ignore: ['assets/**']
//   }
// ];
// const SECTION_TEMPLATE = [
//   {
//     name: 'vue2代码片段',
//     npmName: 'yuye-cli-dev-template-section-vue2',
//     version: 'latest',
//     targetPath: ''
//   },
//   {
//     name: 'vue2代码片段2',
//     npmName: 'yuyd-cli-dev-template-section-vue-template',
//     version: 'latest',
//     targetPath: 'src/'
//   }
// ];
const ADD_MODE_SECTION = 'section';
const ADD_MODE_PAGE = 'page';

const TYPE_CUSTOM = 'custom';
const TYPE_NORMAL = 'normal';

process.on('unhandledRejection', (e) => {});

class AddCommand extends Command {
  init() {
    // 获取add明年的初始化的参数
  }
  async exec() {
    // 代码片段： 以源码形式拷贝的vue组件
    // 1.选择复用方式
    this.addMode = (await this.getAddMode()).addMode;
    if (this.addMode === ADD_MODE_SECTION) {
      // 2. 选择代码片段模板
      await this.installSectionTemplate();
    } else {
      await this.installPageTemplate();
    }
  }

  async getPageTemplate() {
    return await request({
      url: '/page/template'
    });
  }
  async getSectionTemplate() {
    return await request({
      url: '/section/template',
      method: 'get'
    });
  }

  async installSectionTemplate() {
    // 1. 获取页面安装文件夹
    this.dir = process.cwd();
    // 2. 选择页面模板
    this.sectionTemplate = await this.getTemplate(ADD_MODE_SECTION);
    // 3. 安装代码片段模板
    // 3.1 预检查（目录重名）
    await this.prepare(ADD_MODE_SECTION);
    // 3.2 下载页代码片段模板
    await this.downLoadTemplate(ADD_MODE_SECTION);
    // 5. 页面模板安装完成
    await this.installSection();
  }

  async installPageTemplate() {
    // 1. 获取页面安装文件夹
    this.dir = process.cwd();
    // 2. 选择页面模板
    this.pageTemplate = await this.getTemplate(ADD_MODE_PAGE);
    // 3. 安装页面模板
    // 3.1 预检查（目录重名）
    await this.prepare();
    // 3.2 下载页面模板至缓存目录
    await this.downLoadTemplate();
    // 4. 合并页面模板依赖
    // 5. 页面模板安装完成
    await this.installTemplate();
  }

  getAddMode() {
    return inquirer.prompt({
      type: 'list',
      name: 'addMode',
      message: '请选择代码复用模式模式',
      choices: [
        {
          name: '代码片段',
          value: ADD_MODE_SECTION
        },
        {
          name: '页面模板',
          value: ADD_MODE_PAGE
        }
      ]
    });
  }

  async prepare(addMode = ADD_MODE_PAGE) {
    // 生成最终拷贝路径
    if (addMode === ADD_MODE_PAGE) {
      this.targetPath = path.resolve(this.dir, this.pageTemplate.pageName);
    } else {
      this.targetPath = path.resolve(this.dir, 'components', this.sectionTemplate.sectionName);
    }
    if (await pathExists.sync(this.targetPath)) {
      throw new Error('页面文件夹已存在');
    }
  }

  async downLoadTemplate(addMode = ADD_MODE_PAGE) {
    // 模板名称
    const name = addMode === ADD_MODE_PAGE ? '页面' : '代码片段';
    // 缓存文件夹
    const targetPath = path.resolve(userHome, '.yuyd-cli-dev', 'template');
    // 缓存真实路径
    const storeDir = path.resolve(targetPath, 'node_modules');
    const { npmName, version } =
      addMode === ADD_MODE_PAGE ? this.pageTemplate : this.sectionTemplate;

    // 构建Package对象
    const templatePackage = new Package({
      targetPath,
      storeDir,
      packageName: npmName,
      packageVersion: version
    });
    // 页面模板是否存在
    if (!(await templatePackage.exists())) {
      const spinner = spinnerStart(`正在下载${name}模板...`);
      await sleep();
      // 下载模板
      try {
        await templatePackage.install();
      } catch (e) {
        throw e;
      } finally {
        spinner.stop(true);
        if (await templatePackage.exists()) {
          log.success(`下载页面${name}成功`);
          if (addMode === ADD_MODE_PAGE) {
            this.pageTemplatePackage = templatePackage;
          } else {
            this.sectionTemplatePackage = templatePackage;
          }
        }
      }
    } else {
      // 更新模板
      const spinner = spinnerStart(`正在更新${name}模板...`);
      await sleep();
      try {
        await templatePackage.update();
      } catch (e) {
        throw e;
      } finally {
        spinner.stop(true);
        if (await templatePackage.exists()) {
          log.success(`更新${name}成功`);
          if (addMode === ADD_MODE_PAGE) {
            this.pageTemplatePackage = templatePackage;
          } else {
            this.sectionTemplatePackage = templatePackage;
          }
        }
      }
    }
  }

  async installSection() {
    // 1. 选择要插入的源码文件
    let files = [];
    files = fs
      .readdirSync(this.dir, { withFileTypes: true })
      .map((file) => (file.isFile() ? file.name : null))
      .filter((_) => _)
      .map((file) => ({ name: file, value: file }));
    if (files.length === 0) {
      throw new Error('当前文件夹下没有文件！');
    }
    const codeFile = (
      await inquirer.prompt({
        type: 'list',
        message: '请选择需要插入代码片段的源码文件',
        name: 'codeFile',
        choices: files
      })
    ).codeFile;

    // 2. 需要用户插入的行数
    const lineNumber = (
      await inquirer.prompt({
        type: 'input',
        message: '请输入插入的行数：',
        name: 'lineNumber',
        validate: function (value) {
          const done = this.async();
          if (!value || !value.trim()) {
            done('插入的行数不可以为空');
            // 1.1 -> 1
          } else if (value >= 0 && Math.floor(value) === Number(value)) {
            done(null, true);
          } else {
            done('插入的行数必须为整数');
          }
        }
      })
    ).lineNumber;
    log.verbose('codeFile:', codeFile);
    log.verbose('lineNumber:', lineNumber);

    // 3. 对源码文件进行分割成数组
    const codeFilePath = path.resolve(this.dir, codeFile);
    const codeContent = fs.readFileSync(codeFilePath, 'utf-8');
    const codeContentArr = codeContent.split('\n');
    // 4. 已组件的形式插入代码片段
    const componentNameOriginal = this.sectionTemplate.sectionName.toLocaleLowerCase();
    codeContentArr.splice(lineNumber, 0, `<${componentNameOriginal}></${componentNameOriginal}>`);
    // 5. 插入代码片段的import 语句
    const scriptIndex = codeContentArr.findIndex((code) => code.replace(/\s/g, '') === '<script>');
    codeContentArr.splice(
      scriptIndex + 1,
      0,
      `import ${componentNameOriginal} from './components/${this.sectionTemplate.sectionName}/index.vue'`
    );
    log.verbose('codeContentArr:', codeContentArr);
    // 6. 将代码还原为string
    const newCodeContent = codeContentArr.join('\n');
    fs.writeFileSync(codeFilePath, newCodeContent, 'utf-8');
    log.success('代码片段插入成功');
    // 7. 创建代码片段在组件目录
    fse.ensureDirSync(this.targetPath);
    const templatePath = path.resolve(
      this.sectionTemplatePackage.cacheFilePath,
      'template',
      this.sectionTemplate.targetPath
    );
    const targetPath = this.targetPath;
    fse.copy(templatePath, targetPath);
    log.success('代码片段拷贝成功');
  }

  async installTemplate() {
    log.info('正在安装页面模板');
    log.verbose('pageTemplate', this.pageTemplate);
    // 模板路径
    const templatePath = path.resolve(
      this.pageTemplatePackage.cacheFilePath,
      'template',
      this.pageTemplate.targetPath
    );
    // 目标路径
    const targetPath = this.targetPath;

    if (!(await pathExists(templatePath))) {
      throw new Error('页面模板不存在');
    }
    log.verbose('templatePath', templatePath);
    log.verbose('targetPath', this.targetPath);
    fse.ensureDirSync(templatePath);
    fse.ensureDirSync(targetPath);
    if (this.pageTemplate.type === TYPE_CUSTOM) {
      await this.installCustomPageTemplate({ templatePath, targetPath });
    } else {
      await this.installNormalPageTemplate({ templatePath, targetPath });
    }
  }

  async installCustomPageTemplate({ templatePath, targetPath }) {
    // 获取到自定义模板的入口文件
    const rootFile = this.pageTemplatePackage.getRootFilePath();
    if (fs.existsSync(rootFile)) {
      log.notice('开始执行自定义模板');
      const options = {
        templatePath,
        targetPath,
        pageTemplate: this.pageTemplate
      };
      const code = `require('${rootFile}')(${JSON.stringify(options)})`;
      console.log(code);
      await execAsync('node', ['-e', code], { stdio: 'inherit', cwd: process.cwd() });
      log.success('自定义模板安装成功');
    } else {
      throw new Error('自定义模板入口文件不存在');
    }
  }
  async installNormalPageTemplate({ templatePath, targetPath }) {
    fse.copySync(templatePath, targetPath);
    await this.ejsRender({
      targetPath
    });
    await this.dependenciesMerge({ templatePath, targetPath });
    log.success('安装页面模板成功');
  }

  async ejsRender(options) {
    const { targetPath } = options;
    const pageTemplate = this.pageTemplate;
    const { ignore } = pageTemplate;
    return new Promise((resolve, reject) => {
      glob(
        '**',
        {
          cwd: targetPath,
          nodir: true, // 不包含文件夹
          ignore: ignore || ''
        },
        function (err, files) {
          if (err) {
            reject(err);
          } else {
            Promise.all(
              files.map((file) => {
                // 获取文件真实路径
                const filePath = path.resolve(targetPath, file);
                return new Promise((resolve1, reject1) => {
                  // ejs文件渲染，重新拼接
                  ejs.renderFile(
                    filePath,
                    {
                      name: pageTemplate.pageName.toLocaleLowerCase()
                    },
                    {},
                    (err, result) => {
                      if (err) {
                        reject1(err);
                      } else {
                        // 重新写入文件信息
                        fse.writeFileSync(filePath, result);
                        resolve1(result);
                      }
                    }
                  );
                });
              })
            )
              .then(resolve)
              .catch((e) => reject(e));
          }
        }
      );
    });
  }

  async dependenciesMerge(options) {
    function objToArray(o) {
      const arr = [];
      Object.keys(o).forEach((key) => {
        arr.push({
          key,
          value: o[key]
        });
      });
      return arr;
    }
    function arrToObj(arr) {
      const obj = {};
      arr.forEach((item) => {
        obj[item.key] = item.value;
      });
      return obj;
    }
    function depDiff(templateDepArr, targetDepArr) {
      let finalDep = [...targetDepArr];
      templateDepArr.forEach((templateDep) => {
        const duplicatedDep = targetDepArr.find((targetDep) => targetDep.key === templateDep.key);
        if (duplicatedDep) {
          log.verbose('查询到重复依赖:', duplicatedDep);
          const templateRange = semver.validRange(templateDep.value).split('<')[1];
          const targetRange = semver.validRange(duplicatedDep.value).split('<')[1];
          if (templateRange !== targetRange) {
            log.warn(`${templateDep.key}冲突, ${templateDep.value} => ${duplicatedDep.value}`);
          }
        } else {
          log.verbose('查询到新的依赖:', templateDep);
          finalDep.push(templateDep);
        }
      });

      return finalDep;
    }
    // 处理依赖合并问题
    // 1. 获取package.json
    const { templatePath, targetPath } = options;
    const templatePkgPath = pkgUp.sync({ cwd: templatePath });
    const targetPkgPath = pkgUp.sync({ cwd: targetPath });
    const templatePkg = fse.readJSONSync(templatePkgPath);
    const targetPkg = fse.readJSONSync(targetPkgPath);
    // 2. 获取dependencies
    const templateDependencies = templatePkg.dependencies || {};
    const targetDependencies = targetPkg.dependencies || {};
    // 3. 将对象转换成数组
    const templateDepArr = objToArray(templateDependencies);
    const targetDepArr = objToArray(targetDependencies);
    // 4. 实现dep之间的diff
    const newDep = depDiff(templateDepArr, targetDepArr);
    targetPkg.dependencies = arrToObj(newDep);
    fse.writeJSONSync(targetPkgPath, targetPkg, { spaces: 2 });
    // 5. 自动安装依赖
    log.info('正在安装页面模板的依赖');
    await this.execCommand('npm install', path.dirname(targetPkgPath));
    log.success('安装页面模板依赖成功');
  }

  async execCommand(command, cwd) {
    let ret;
    if (command) {
      // npm install => [npm, install]
      const cmdArray = command.split(' ');
      const cmd = cmdArray[0];
      const args = cmdArray.slice(1);
      ret = await execAsync(cmd, args, {
        stdio: 'inherit',
        cwd
      });
      if (ret === 0) {
        throw new Error(command + '命令执行失败');
      }
      return ret;
    }
  }

  async getTemplate(addMode = ADD_MODE_PAGE) {
    const name = addMode === ADD_MODE_PAGE ? '页面' : '代码';
    // 通过API获取页面模板列表
    if (addMode === ADD_MODE_PAGE) {
      const pageTemplateData = await this.getPageTemplate();
      this.pageTemplateData = pageTemplateData;
    } else {
      const sectionTemplateData = await this.getSectionTemplate();
      this.sectionTemplateData = sectionTemplateData;
    }
    const TEMPLATE = addMode === ADD_MODE_PAGE ? this.pageTemplateData : this.sectionTemplateData;

    const pageTemplateName = (
      await inquirer.prompt({
        type: 'list',
        name: 'pageTemplate',
        message: '请选择' + name + '模板',
        choices: this.createChoices(addMode)
      })
    ).pageTemplate;

    // 2.1 输入页面名称
    const pageTemplate = TEMPLATE.find((item) => item.npmName === pageTemplateName);
    if (!pageTemplate) {
      throw new Error(name + '模板不存在！');
    }
    const { pageName } = await inquirer.prompt({
      type: 'input',
      name: 'pageName',
      message: '请输入' + name + '名称',
      default: '',
      validate: function (value) {
        const done = this.async();
        if (!value || !value.trim()) {
          done('请输入' + name + '名称');
          return;
        }
        done(null, true);
      }
    });
    if (addMode === ADD_MODE_PAGE) {
      pageTemplate.pageName = pageName.trim();
    } else {
      pageTemplate.sectionName = pageName.trim();
    }
    return pageTemplate;
  }

  createChoices(addMode = ADD_MODE_PAGE) {
    const templateList =
      addMode === ADD_MODE_PAGE ? this.pageTemplateData : this.sectionTemplateData;
    return templateList.map((item) => ({
      name: item.name,
      value: item.npmName
    }));
  }
}

function add(argv) {
  log.verbose('argv', argv);
  return new AddCommand(argv);
}

module.exports = add;
module.exports.AddCommand = AddCommand;
